﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.Collections.Specialized;

namespace Cartoons
{
    public partial class SearchResultViewer : UserControl
    {
        private Point hideDestinationPoint;
        private double hideDestinationSize;
        public SearchResultViewer()
        {
            InitializeComponent();
            hideDestinationPoint = new Point(8, 8);
            hideDestinationSize = 20;
        }

        private void image_ImageOpenSucceeded(object sender, RoutedEventArgs e)
        {
            foreach (var img in image.SubImages)
            {
                img.ViewportOrigin = hideDestinationPoint;
                img.ViewportWidth = hideDestinationSize;
            }

            if (Indexes != null)
                PlaceElements();
        }

        Storyboard animationStoryboard;
        private void PlaceElements()
        {
            if (!ImagesLoaded())
                return;

            image.ViewportOrigin = new Point(0, 0);
            image.ViewportWidth = 1;
            currentZoom = 1;

            if (animationStoryboard != null)
                animationStoryboard.Pause();

            animationStoryboard = new Storyboard();

            var rowSize = CalcRowSize(FindAspectRatio());

            for (short i = 0; i < image.SubImages.Count; i++)
                if (!Indexes.Contains(i))
                    AnimateImageMove(i, hideDestinationPoint, hideDestinationSize);

            for (int i = 0; i < Indexes.Length; i++)
                AnimateImageMove(Indexes.OrderBy(j => j).ToArray()[i], new Point((-i % rowSize) * (1 + 1 / (double)rowSize), (-i / rowSize)), rowSize + 1);

            animationStoryboard.Begin();
        }

        private void AnimateImageMove(short imageIndex, Point destinationPoint, double? destinationSize = null)
        {
            var img = image.SubImages[imageIndex];
            destinationPoint.Y /= FindAspectRatio();

            var flyOutAnimation = new PointAnimation() { From = img.ViewportOrigin, To = destinationPoint, Duration = Duration };
            //flyOutAnimation.EasingFunction = new CircleEase();
            Storyboard.SetTarget(flyOutAnimation, img);
            Storyboard.SetTargetProperty(flyOutAnimation, new PropertyPath("ViewportOrigin"));
            animationStoryboard.Children.Add(flyOutAnimation);

            var changeSizeAnimation = new DoubleAnimation() { From = img.ViewportWidth, To = destinationSize ?? img.ViewportWidth, Duration = Duration };
            //flyOutAnimation.EasingFunction = new CircleEase();
            Storyboard.SetTarget(changeSizeAnimation, img);
            Storyboard.SetTargetProperty(changeSizeAnimation, new PropertyPath("ViewportWidth"));
            animationStoryboard.Children.Add(changeSizeAnimation);
        }

        private bool ImagesLoaded()
        {
            return image.SubImages.Count > 0;
        }

        private double? aspectRatioCached;
        private double FindAspectRatio()
        {
            if (aspectRatioCached != null)
                return aspectRatioCached.Value;

            aspectRatioCached = image.SubImages.OrderBy(i => Math.Abs(i.AspectRatio - 1)).Last().AspectRatio;
            return aspectRatioCached.Value;
        }

        private int CalcRowSize(double aspectRatio)
        {
            if (Indexes == null || Indexes.Length == 0)
                return 0;

            return (int)Math.Ceiling(Math.Sqrt(Indexes.Length * this.ActualWidth / (this.ActualHeight * aspectRatio)));
        }

        #region Moving

        private Point? prevMousePosition;
        private Point mouseDownPosition;
        private Point viewportOrigin;
        private void UserControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            CaptureMouse();

            prevMousePosition = e.GetPosition(image);
            mouseDownPosition = prevMousePosition.Value;
            viewportOrigin = image.ViewportOrigin;
        }

        private void UserControl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            ReleaseMouseCapture();

            if (prevMousePosition != null && Distance(e.GetPosition(image), mouseDownPosition) < 3)
            {
                ProcessMouseClick(e.GetPosition(image));
            }

            prevMousePosition = null;
        }

        private void ProcessMouseClick(Point point)
        {
            SelectedIndex = GetSubimageIndexUnderPoint(point);
        }

        private void SelectImage(short index)
        {
            image.SubImages[index].ZIndex = 100;
            //ShowImageHighlight(index);
        }

        private void ClearSelection(short index)
        {
            image.SubImages[index].ZIndex = 0;
            //HideImageHighlight();
        }

        private short? GetSubimageIndexUnderPoint(Point point)
        {
            for (short i = 0; i < image.SubImages.Count; i++)
            {
                if (GetSubImageRect(i).Contains(point))
                    return i;
            }
            return null;
        }

        private double Distance(Point p1, Point p2)
        {
            return Math.Sqrt(Math.Pow(p2.X - p1.X, 2) + Math.Pow(p2.Y - p1.Y, 2));
        }

        private void UserControl_MouseMove(object sender, MouseEventArgs e)
        {
            //if (GetSubimageIndexUnderPoint(e.GetPosition(image)) == null)
            //    image.Cursor = Cursors.Arrow;
            //else
            //    image.Cursor = Cursors.Hand;

            if (prevMousePosition != null)
            {
                var currPosition = e.GetPosition(image);
                var currPositionRelative = image.ElementToLogicalPoint(currPosition);
                var prevPositionRelative = image.ElementToLogicalPoint(prevMousePosition.Value);

                viewportOrigin = new Point(viewportOrigin.X - (currPositionRelative.X - prevPositionRelative.X), viewportOrigin.Y - (currPositionRelative.Y - prevPositionRelative.Y));
                image.ViewportOrigin = viewportOrigin;
                prevMousePosition = currPosition;

                CoreectViewportOrigin();
            }
        }

        private void CoreectViewportOrigin()
        {
            double leftBound = -0.3 / currentZoom;
            double topBound = -0.3 / currentZoom;
            double rightBound = 1.0 - 0.3 / currentZoom;
            double bottomBound = 1.0 * image.ActualHeight / image.ActualWidth - 0.3 / currentZoom;
            if (image.ViewportOrigin.X < leftBound)
            {
                image.ViewportOrigin = new Point(leftBound, image.ViewportOrigin.Y);
            }
            if (image.ViewportOrigin.Y < topBound)
            {
                image.ViewportOrigin = new Point(image.ViewportOrigin.X, -topBound);
            }
            if (image.ViewportOrigin.X > rightBound)
            {
                image.ViewportOrigin = new Point(rightBound, image.ViewportOrigin.Y);
            }
            if (image.ViewportOrigin.Y > bottomBound)
            {
                image.ViewportOrigin = new Point(image.ViewportOrigin.X, bottomBound);
            }
        }

        private double currentZoom = 1;
        private void UserControl_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            var p = image.ElementToLogicalPoint(new Point(image.ActualWidth / 2, image.ActualHeight / 2));
            if (Math.Sign(e.Delta) > 0)
            {
                image.ZoomAboutLogicalPoint(1.1, p.X, p.Y);
                currentZoom *= 1.1;
            }
            else
            {
                if (currentZoom / 1.1 >= 1)
                {
                    image.ZoomAboutLogicalPoint(1 / 1.1, p.X, p.Y);
                    currentZoom /= 1.1;
                }
            }
            CoreectViewportOrigin();
        }

        #endregion

        #region Highlighting

        public void ShowImageHighlight(int nSubImageIndex)
        {
            // Hightlight the image by drawing a border around it    
            int borderWidth = 2;
            // Setting the default border width to 2    
            if (image.ViewportWidth < 1)
                // This adjusts for zoomed images where a border of 2 is insufficient        
                borderWidth = (int)(borderWidth / image.ViewportWidth);
            Rect borderRect = ExpandRect(GetSubImageRect(nSubImageIndex), borderWidth);
            imageBorder.Width = borderRect.Width;
            imageBorder.Height = borderRect.Height;
            imageBorder.SetValue(Canvas.LeftProperty, borderRect.X);
            imageBorder.SetValue(Canvas.TopProperty, borderRect.Y);
            imageBorder.BorderThickness = new Thickness(borderWidth);
            imageBorder.Opacity = 1.0;
        }

        private Rect GetSubImageRect(int nSubImageIndex)
        {
            MultiScaleSubImage subImage = image.SubImages[nSubImageIndex];
            Point topLeft = subImage.ViewportOrigin;
            topLeft.X = -(topLeft.X / subImage.ViewportWidth);
            topLeft.Y = -(topLeft.Y / subImage.ViewportWidth);
            double width = 1 / subImage.ViewportWidth;
            double height = width / subImage.AspectRatio;
            Point bottomright = new Point(topLeft.X + width, topLeft.Y + height);
            topLeft = image.LogicalToElementPoint(topLeft);
            bottomright = image.LogicalToElementPoint(bottomright);
            return new Rect(topLeft, bottomright);
        }

        public void HideImageHighlight()
        {
            imageBorder.Opacity = 0.0;
        }

        Rect ExpandRect(Rect rect, int expandBy)
        {
            return new Rect(rect.Left - expandBy, rect.Top - expandBy, rect.Width + expandBy * 2, rect.Height + expandBy * 2);
        }

        #endregion

    }
}